In [289]:
import pandas as pd
def get_pdb_divide_params(frequency, F_BUS=int(48e6)):
mult_factor = np.array([1, 10, 20, 40])
prescaler = np.arange(8)
clock_divide = (pd.DataFrame([[i, m, p, m * (1 << p)]
for i, m in enumerate(mult_factor) for p in prescaler],
columns=['mult_', 'mult_factor', 'prescaler', 'combined'])
.drop_duplicates(subset=['combined'])
.sort_values('combined', ascending=True))
clock_divide['clock_mod'] = (F_BUS / frequency / clock_divide.combined).astype(int)
return clock_divide.loc[clock_divide.clock_mod <= 0xffff]
In [304]:
PDB0_IDLY = 0x4003600C # Interrupt Delay Register
PDB0_SC = 0x40036000 # Status and Control Register
PDB0_MOD = 0x40036004 # Modulus Register
PDB_SC_PDBEIE = 0x00020000 # Sequence Error Interrupt Enable
PDB_SC_SWTRIG = 0x00010000 # Software Trigger
PDB_SC_DMAEN = 0x00008000 # DMA Enable
PDB_SC_PDBEN = 0x00000080 # PDB Enable
PDB_SC_PDBIF = 0x00000040 # PDB Interrupt Flag
PDB_SC_PDBIE = 0x00000020 # PDB Interrupt Enable.
PDB_SC_CONT = 0x00000002 # Continuous Mode Enable
PDB_SC_LDOK = 0x00000001 # Load OK
def PDB_SC_TRGSEL(n): return (((n) & 15) << 8) # Trigger Input Source Select
def PDB_SC_PRESCALER(n): return (((n) & 7) << 12) # Prescaler Divider Select
def PDB_SC_MULT(n): return (((n) & 3) << 2) # Multiplication Factor
def PDB_SC_LDMOD(n): return (((n) & 3) << 18) # Load Mode Select
# PDB0_IDLY = 1; // the pdb interrupt happens when IDLY is equal to CNT+1
proxy.mem_cpy_host_to_device(PDB0_IDLY, np.uint32(1).tostring())
# software trigger enable PDB continuous
PDB_CONFIG = (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_CONT | PDB_SC_LDMOD(0))
PDB0_SC_ = (PDB_CONFIG | PDB_SC_PRESCALER(clock_divide.prescaler) |
PDB_SC_MULT(clock_divide.mult_) |
PDB_SC_DMAEN | PDB_SC_LDOK) # load all new values
proxy.mem_cpy_host_to_device(PDB0_SC, np.uint32(PDB0_SC_).tostring())
In [310]:
clock_divide = get_pdb_divide_params(25).iloc[0]
# PDB0_MOD = (uint16_t)(mod-1);
proxy.mem_cpy_host_to_device(PDB0_MOD, np.uint32(clock_divide.clock_mod).tostring())
PDB0_SC_ = (PDB_CONFIG | PDB_SC_PRESCALER(clock_divide.prescaler) |
PDB_SC_DMAEN | PDB_SC_MULT(clock_divide.mult_) |
PDB_SC_SWTRIG) # start the counter!
proxy.mem_cpy_host_to_device(PDB0_SC, np.uint32(PDB0_SC_).tostring())
In [311]:
PDB0_SC_ = 0
proxy.mem_cpy_host_to_device(PDB0_SC, np.uint32(PDB0_SC_).tostring())
Use linked DMA channels to perform "scan" across multiple ADC input channels.
After each scan, use DMA scatter chain to write the converted ADC values to a
separate output array for each ADC channel. The length of the output array to
allocate for each ADC channel is determined by the sample_count
in the
example below.
See diagram below.
SC1A
configurations to the ADC SC1A
register. Each SC1A
configuration selects an analog input channel.DMA_SSRT = i
), starting the ADC conversion for the first ADC
channel configuration.COCO
), and
copies the output result of the ADC to consecutive locations in the result
array.SC1A
configuration to be loaded immediately
after the current ADC result has been copied to the result array.SC1A
table.
In [260]:
import arduino_helpers.hardware.teensy as teensy
from arduino_rpc.protobuf import resolve_field_values
from teensy_minimal_rpc import SerialProxy
import teensy_minimal_rpc.DMA as DMA
import teensy_minimal_rpc.ADC as ADC
import teensy_minimal_rpc.SIM as SIM
import teensy_minimal_rpc.PIT as PIT
# Disconnect from existing proxy (if available)
try:
del proxy
except NameError:
pass
proxy = SerialProxy()
proxy.pin_mode(teensy.LED_BUILTIN, 1)
In [261]:
from IPython.display import display
proxy.update_sim_SCGC6(SIM.R_SCGC6(PDB=True))
sim_scgc6 = SIM.R_SCGC6.FromString(proxy.read_sim_SCGC6().tostring())
display(resolve_field_values(sim_scgc6)[['full_name', 'value']].T)
# proxy.update_pit_registers(PIT.Registers(MCR=PIT.R_MCR(MDIS=False)))
# pit_registers = PIT.Registers.FromString(proxy.read_pit_registers().tostring())
# display(resolve_field_values(pit_registers)[['full_name', 'value']].T)
In [266]:
import numpy as np
# CORE_PIN13_PORTSET = CORE_PIN13_BITMASK;
# CORE_PIN13_PORTCLEAR = CORE_PIN13_BITMASK;
#define CORE_PIN13_PORTCLEAR GPIOC_PCOR
#define CORE_PIN13_PORTSET GPIOC_PSOR
#define GPIOC_PCOR (*(volatile uint32_t *)0x400FF088) // Port Clear Output Register
#define GPIOC_PSOR (*(volatile uint32_t *)0x400FF084) // Port Set Output Register
CORE_PIN13_BIT = 5
GPIOC_PCOR = 0x400FF088 # Port Clear Output Register
GPIOC_PSOR = 0x400FF084 # Port Set Output Register
proxy.mem_cpy_host_to_device(GPIOC_PSOR, np.uint32(1 << CORE_PIN13_BIT).tostring())
In [299]:
proxy.update_dma_mux_chcfg(0, DMA.MUX_CHCFG(ENBL=1, TRIG=0, SOURCE=48))
proxy.update_dma_registers(DMA.Registers(SERQ=0))
Out[299]:
In [298]:
proxy.update_dma_registers(DMA.Registers(CERQ=0))
Out[298]:
In [117]:
resolve_field_values(DMA.MUX_CHCFG.FromString(proxy.read_dma_mux_chcfg(0).tostring()))[['full_name', 'value']]
Out[117]:
In [114]:
print proxy.update_pit_timer_config(0, PIT.TimerConfig(LDVAL=int(48e6)))
print proxy.update_pit_timer_config(0, PIT.TimerConfig(TCTRL=PIT.R_TCTRL(TEN=True)))
pit0 = PIT.TimerConfig.FromString(proxy.read_pit_timer_config(0).tostring())
display(resolve_field_values(pit0)[['full_name', 'value']].T)
In [4]:
PIT_LDVAL0 = 0x40037100 # Timer Load Value Register
PIT_CVAL0 = 0x40037104 # Current Timer Value Register
PIT_TCTRL0 = 0x40037108 # Timer Control Register
proxy.mem_cpy_host_to_device(PIT_TCTRL0, np.uint32(1).tostring())
proxy.mem_cpy_device_to_host(PIT_TCTRL0, 4).view('uint32')[0]
Out[4]:
In [287]:
proxy.digital_write(teensy.LED_BUILTIN, 0)
In [273]:
proxy.update_dma_registers(DMA.Registers(SSRT=0))
Out[273]:
In [288]:
proxy.free_all()
toggle_pin_addr = proxy.mem_alloc(4)
proxy.mem_cpy_host_to_device(toggle_pin_addr, np.uint32(1 << CORE_PIN13_BIT).tostring())
tcds_addr = proxy.mem_aligned_alloc(32, 2 * 32)
hw_tcds_addr = 0x40009000
tcd_addrs = [tcds_addr + 32 * i for i in xrange(2)]
# Create Transfer Control Descriptor configuration for first chunk, encoded
# as a Protocol Buffer message.
tcd0_msg = DMA.TCD(CITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ITER=1),
BITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ITER=1),
ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._32_BIT,
DSIZE=DMA.R_TCD_ATTR._32_BIT),
NBYTES_MLNO=4,
SADDR=int(toggle_pin_addr),
SOFF=0,
SLAST=0,
DADDR=int(GPIOC_PSOR),
DOFF=0,
# DLASTSGA=0,
# CSR=DMA.R_TCD_CSR(START=0, DONE=False, ESG=False))
# proxy.update_dma_TCD(0, tcd0_msg)
DLASTSGA=int(tcd_addrs[1]),
CSR=DMA.R_TCD_CSR(START=0, DONE=False, ESG=True))
# # Convert Protocol Buffer encoded TCD to bytes structure.
tcd0 = proxy.tcd_msg_to_struct(tcd0_msg)
# Create binary TCD struct for each TCD protobuf message and copy to device
# memory.
for i in xrange(2):
tcd_i = tcd0.copy()
tcd_i['DADDR'] = [GPIOC_PSOR, GPIOC_PCOR][i]
tcd_i['DLASTSGA'] = tcd_addrs[(i + 1) % len(tcd_addrs)]
tcd_i['CSR'] |= (1 << 4)
proxy.mem_cpy_host_to_device(tcd_addrs[i], tcd_i.tostring())
# Load initial TCD in scatter chain to DMA channel chosen to handle scattering.
proxy.mem_cpy_host_to_device(hw_tcds_addr, tcd0.tostring())
In [61]:
proxy.update_dma_registers(DMA.Registers(SSRT=0))
Out[61]:
In [ ]:
dma_channel_scatter = 0
dma_channel_i = 1
dma_channel_ii = 2
In [ ]:
# Set ADC parameters
proxy.setAveraging(16, teensy.ADC_0)
proxy.setResolution(16, teensy.ADC_0)
proxy.setConversionSpeed(teensy.ADC_MED_SPEED, teensy.ADC_0)
proxy.setSamplingSpeed(teensy.ADC_MED_SPEED, teensy.ADC_0)
proxy.update_adc_registers(
teensy.ADC_0,
ADC.Registers(CFG2=ADC.R_CFG2(MUXSEL=ADC.R_CFG2.B)))
Pseudo-code to set DMA channel $i$ to be triggered by ADC0 conversion complete.
DMAMUX0_CFGi[SOURCE] = DMAMUX_SOURCE_ADC0 // Route ADC0 as DMA channel source.
DMAMUX0_CFGi[TRIG] = 0 // Disable periodic trigger.
DMAMUX0_CFGi[ENBL] = 1 // Enable the DMAMUX configuration for channel.
DMA_ERQ[i] = 1 // DMA request input signals and this enable request flag
// must be asserted before a channel’s hardware service
// request is accepted (21.3.3/394).
DMA_SERQ = i // Can use memory mapped convenience register to set instead.
In [ ]:
DMAMUX_SOURCE_ADC0 = 40 # from `kinetis.h`
DMAMUX_SOURCE_ADC1 = 41 # from `kinetis.h`
# DMAMUX0_CFGi[SOURCE] = DMAMUX_SOURCE_ADC0 // Route ADC0 as DMA channel source.
# DMAMUX0_CFGi[TRIG] = 0 // Disable periodic trigger.
# DMAMUX0_CFGi[ENBL] = 1 // Enable the DMAMUX configuration for channel.
proxy.update_dma_mux_chcfg(dma_channel_ii,
DMA.MUX_CHCFG(SOURCE=DMAMUX_SOURCE_ADC0,
TRIG=False,
ENBL=True))
# DMA request input signals and this enable request flag
# must be asserted before a channel’s hardware service
# request is accepted (21.3.3/394).
# DMA_SERQ = i
proxy.update_dma_registers(DMA.Registers(SERQ=dma_channel_ii))
proxy.enableDMA(teensy.ADC_0)
In [ ]:
proxy.DMA_registers().loc['']
In [ ]:
dmamux = DMA.MUX_CHCFG.FromString(proxy.read_dma_mux_chcfg(dma_channel_ii).tostring())
resolve_field_values(dmamux)[['full_name', 'value']]
In [ ]:
adc0 = ADC.Registers.FromString(proxy.read_adc_registers(teensy.ADC_0).tostring())
resolve_field_values(adc0)[['full_name', 'value']].loc[['CFG2', 'SC1A', 'SC3']]
In [ ]:
import re
import numpy as np
import pandas as pd
import arduino_helpers.hardware.teensy.adc as adc
# The number of samples to record for each ADC channel.
sample_count = 10
teensy_analog_channels = ['A0', 'A1', 'A0', 'A3', 'A0']
sc1a_pins = pd.Series(dict([(v, adc.CHANNEL_TO_SC1A_ADC0[getattr(teensy, v)])
for v in dir(teensy) if re.search(r'^A\d+', v)]))
channel_sc1as = np.array(sc1a_pins[teensy_analog_channels].tolist(), dtype='uint32')
In [ ]:
proxy.free_all()
N = np.dtype('uint16').itemsize * channel_sc1as.size
# Allocate source array
adc_result_addr = proxy.mem_alloc(N)
# Fill result array with zeros
proxy.mem_fill_uint8(adc_result_addr, 0, N)
# Copy channel SC1A configurations to device memory
adc_sda1s_addr = proxy.mem_aligned_alloc_and_set(4, channel_sc1as.view('uint8'))
# Allocate source array
samples_addr = proxy.mem_alloc(sample_count * N)
tcds_addr = proxy.mem_aligned_alloc(32, sample_count * 32)
hw_tcds_addr = 0x40009000
tcd_addrs = [tcds_addr + 32 * i for i in xrange(sample_count)]
hw_tcd_addrs = [hw_tcds_addr + 32 * i for i in xrange(sample_count)]
# Fill result array with zeros
proxy.mem_fill_uint8(samples_addr, 0, sample_count * N)
# Create Transfer Control Descriptor configuration for first chunk, encoded
# as a Protocol Buffer message.
tcd0_msg = DMA.TCD(CITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ITER=1),
BITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ITER=1),
ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._16_BIT,
DSIZE=DMA.R_TCD_ATTR._16_BIT),
NBYTES_MLNO=channel_sc1as.size * 2,
SADDR=int(adc_result_addr),
SOFF=2,
SLAST=-channel_sc1as.size * 2,
DADDR=int(samples_addr),
DOFF=2 * sample_count,
DLASTSGA=int(tcd_addrs[1]),
CSR=DMA.R_TCD_CSR(START=0, DONE=False, ESG=True))
# Convert Protocol Buffer encoded TCD to bytes structure.
tcd0 = proxy.tcd_msg_to_struct(tcd0_msg)
# Create binary TCD struct for each TCD protobuf message and copy to device
# memory.
for i in xrange(sample_count):
tcd_i = tcd0.copy()
tcd_i['SADDR'] = adc_result_addr
tcd_i['DADDR'] = samples_addr + 2 * i
tcd_i['DLASTSGA'] = tcd_addrs[(i + 1) % len(tcd_addrs)]
tcd_i['CSR'] |= (1 << 4)
proxy.mem_cpy_host_to_device(tcd_addrs[i], tcd_i.tostring())
# Load initial TCD in scatter chain to DMA channel chosen to handle scattering.
proxy.mem_cpy_host_to_device(hw_tcd_addrs[dma_channel_scatter],
tcd0.tostring())
print 'ADC results:', proxy.mem_cpy_device_to_host(adc_result_addr, N).view('uint16')
print 'Analog pins:', proxy.mem_cpy_device_to_host(adc_sda1s_addr, len(channel_sc1as) *
channel_sc1as.dtype.itemsize).view('uint32')
In [ ]:
ADC0_SC1A = 0x4003B000 # ADC status and control registers 1
sda1_tcd_msg = DMA.TCD(CITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ELINK=False, ITER=channel_sc1as.size),
BITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ELINK=False, ITER=channel_sc1as.size),
ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._32_BIT,
DSIZE=DMA.R_TCD_ATTR._32_BIT),
NBYTES_MLNO=4,
SADDR=int(adc_sda1s_addr),
SOFF=4,
SLAST=-channel_sc1as.size * 4,
DADDR=int(ADC0_SC1A),
DOFF=0,
DLASTSGA=0,
CSR=DMA.R_TCD_CSR(START=0, DONE=False))
proxy.update_dma_TCD(dma_channel_i, sda1_tcd_msg)
In [ ]:
ADC0_RA = 0x4003B010 # ADC data result register
ADC0_RB = 0x4003B014 # ADC data result register
tcd_msg = DMA.TCD(CITER_ELINKYES=DMA.R_TCD_ITER_ELINKYES(ELINK=True, LINKCH=1, ITER=channel_sc1as.size),
BITER_ELINKYES=DMA.R_TCD_ITER_ELINKYES(ELINK=True, LINKCH=1, ITER=channel_sc1as.size),
ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._16_BIT,
DSIZE=DMA.R_TCD_ATTR._16_BIT),
NBYTES_MLNO=2,
SADDR=ADC0_RA,
SOFF=0,
SLAST=0,
DADDR=int(adc_result_addr),
DOFF=2,
DLASTSGA=-channel_sc1as.size * 2,
CSR=DMA.R_TCD_CSR(START=0, DONE=False,
MAJORELINK=True,
MAJORLINKCH=dma_channel_scatter))
proxy.update_dma_TCD(dma_channel_ii, tcd_msg)
In [ ]:
# Clear output array to zero.
proxy.mem_fill_uint8(adc_result_addr, 0, N)
proxy.mem_fill_uint8(samples_addr, 0, sample_count * N)
# Software trigger channel $i$ to copy *first* SC1A configuration, which
# starts ADC conversion for the first channel.
#
# Conversions for subsequent ADC channels are triggered through minor-loop
# linking from DMA channel $ii$ to DMA channel $i$ (*not* through explicit
# software trigger).
print 'ADC results:'
for i in xrange(sample_count):
proxy.update_dma_registers(DMA.Registers(SSRT=dma_channel_i))
# Display converted ADC values (one value per channel in `channel_sd1as` list).
print ' Iteration %s:' % i, proxy.mem_cpy_device_to_host(adc_result_addr, N).view('uint16')
print ''
print 'Samples by channel:'
# Trigger once per chunk
# for i in xrange(sample_count):
# proxy.update_dma_registers(DMA.Registers(SSRT=0))
device_dst_data = proxy.mem_cpy_device_to_host(samples_addr, sample_count * N)
pd.DataFrame(device_dst_data.view('uint16').reshape(-1, sample_count).T,
columns=teensy_analog_channels)
In [ ]: